package kmeans.classificazione;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import kmeans.documenti.Documento;


public class Classificatore {
	private int K;
	private List<Documento> docs;
	private String[] classi;
	private Map<String, List<Documento>> map_docs;
	private String stampa;
	
	public Classificatore(int k, List<Documento> docs,Map<String, List<Documento>> map_docs) {
		K = k;
		this.setDocs(docs);
		this.setClassi(new String[k]);
		this.setMap_docs(map_docs);
		this.stampa ="";
	}
/* Algoritmo di classificazione, divide le istanze in k gruppi in base alla vicinanza dai centri;
 * Ad ogni iterazione ricalcola i centri dei k gruppi come distanza vettoriale media.
 */
	public Map<Integer, List<Documento>> algoritmo_K_Means(List<Documento> docs, List<Double[]> centri, int K){
		int cambiamenti,i,iterazioni = 0;
		String stampa = this.getStampa();
		stampa += "\nSistemo l'insieme di documenti in ingresso\n";
		Integer  cat = null;
		double dist,min;
		Map<Integer, List<Documento>> map = new HashMap<Integer, List<Documento>>();
		System.out.println("Analizzo l'insieme di documenti in ingresso");
		do{
			cambiamenti = 0;
			for(Documento d: docs){
				min = 10000;
				for(i=0; i<K; i++){
					dist = calcoloDistanza(d.getVector(), centri.get(i));
					if(dist<min){
						min = dist;
						cat = i;
					}
				}
				if(d.getClassePredetta()!=null && d.getClassePredetta()==cat){}
				else{
						if(!map.containsKey(cat)){
							d.setClassePredetta(cat);
							List<Documento> lista = new LinkedList<Documento>();
							lista.add(d);
							map.put(cat, lista);
							cambiamenti++;
						}
						else{
							if(d.getClassePredetta()!=null){
								map.get(d.getClassePredetta()).remove(d);
							}
							d.setClassePredetta(cat);
							map.get(cat).add(d);
							cambiamenti++;
						}
					}
			}
			centri = calcolaCentri(map);
			iterazioni++;
			stampa += "iterazione "+iterazioni + " numeroCambiamenti "+cambiamenti+ "\n";
			System.out.println("iterazione "+iterazioni + " numeroCambiamenti "+cambiamenti);
		}while(!(cambiamenti == 0)|| (iterazioni>30 && cambiamenti<=1));
		stampa += "numero iterazioni "+iterazioni +"\n\n\n";
		System.out.println("numero iterazioni "+iterazioni);
		this.setStampa(stampa);
		return map;
	}
	

	/*Metodi per stampare i risultati ottenuti su file*/
	public String getStampa() {
		return stampa;
	}

	public void setStampa(String stampa) {
		this.stampa = stampa;
	}

	/*Metodo che calcola i nuovi centri dei K gruppi*/
	private List<Double[]> calcolaCentri(Map<Integer, List<Documento>> map) {
		List<Double[]> centri = new LinkedList<Double[]>();
		if(map!=null){
			for(Integer i: map.keySet()){
				centri.add(this.calcoloCentro(i, map.get(i)));
			}
		}
		return centri;
	}

	/*Calcolo del centro di uno specifico gruppo*/
	private Double[] calcoloCentro(Integer cat, List<Documento> list) {
		Double[] centro = null;
		int i,j;
		if(list!=null){
			int l = list.get(0).getVector().length;
			centro = new Double[l];
			for(i=0; i<list.size(); i++){
				for(j=0; j<l; j++){
					if(centro[j]== null){
						centro[j] = 0.0;
					}
					centro[j] += list.get(i).getVector()[j];
				}
			}
			for(i=0; i<centro.length; i++){
				centro[i]= centro[i]/list.size();
			}
		}
		return centro;
		
	}

	/*metodo per calcolare la distanza vettoriale tra due array di double*/
	private double calcoloDistanza(double[] v1, Double[] v2) {
			int i;
			double d = 0;
			if(v1.length == v2.length){
				for(i=0; i<v1.length; i++){
					d += Math.pow((v1[i]-v2[i]), 2);
				}
			}
			d = Math.sqrt(d);
			return d;
		}
	
	/*genera la matrice gruppo-classeReale in base ai risultati ottenuti dalla classificazione*/
	public int[][] risultatiClassificatore(Map<Integer, List<Documento>> map, Set<String> categorie){
		int i,j, k;
		String[] cat = new String[categorie.size()];
		k = 0;
		
		System.out.println("risultati classificatore");
		for(String s: categorie){
			cat[k] = s;
			k++;
		}
		this.setClassi(cat);
	
		int[][] matrice = new int[map.keySet().size()+1][map.keySet().size()+1];
		for(i=0; i<matrice.length; i++){
			for(j=0; j<matrice.length; j++){
				matrice[i][j] = 0;
			}
		}
		for(Integer index: map.keySet()){			
			for(Documento d: map.get(index)){
				for(i=0; i<cat.length; i++){
					if(d.getCategoria().equals(cat[i])){
						matrice[i][index] += 1; 
					}
				}
			}
		}
		int sum = 0;
		for(i=0; i<matrice.length-1; i++){
			sum = 0;
			for(j=0; j<matrice.length-1; j++){
				sum = sum + matrice[i][j];
			}
			matrice[i][matrice.length-1] = sum;
		}
		for(i=0; i<matrice.length-1; i++){
			sum = 0;
			for(j=0; j<matrice.length-1; j++){
				sum = sum + matrice[j][i];
			}
			matrice[matrice.length-1][i] = sum;
		}
		sum = 0;
		for(i=0; i<matrice.length-1; i++){
			sum = sum +matrice[matrice.length-1][i];
		}
		matrice[matrice.length-1][matrice.length-1] = sum;
		
		String stampa = this.getStampa();
		stampa += "Risulati del classificatore: \n\n\t\t";
		for(Integer in: map.keySet()){
			stampa +=in+"\t\t";
		}
		stampa +="\n";
		for(i=0; i<matrice.length; i++){
			if(i<matrice.length-1){
				if(cat[i].equals("interest")|| cat[i].equals("money-fx") || cat[i].equals("acq")){
					if(cat[i].equals("acq")){
						stampa += cat[i]+"\t\t\t";
					}
					else{stampa += cat[i]+"\t";}
				}
				else{stampa += cat[i]+"\t\t";}
			}
			else{
				stampa += "\t\t";}
			
			for(j=0;j<matrice.length; j++){
				stampa += matrice[i][j]+"\t\t";
			}
			stampa += "\n";
		}
		this.setStampa(stampa);
		return matrice;
	}
	
	/* Metodo per stabilire, tramite la matrice gruppo-classeReale, per ogni gruppo quale
	 * categoria predirre in base al valore massimo recuperato
	 * */
	public Map<String, List<Integer>> assegnaCategorie(int[][] matrice, String[] categorie){
		int i, j, index = -1;
		int max;
		String stampa = this.getStampa();
		Map<String, List<Integer>> catToInt = new HashMap<String, List<Integer>>();
		for(i=0; i<matrice.length-1; i++){
			max = 0;
			for(j=0; j<matrice.length-1; j++){
				if(matrice[j][i]>=max){
					max = matrice[j][i];
					index = j;
				}
			}
			if(index!=-1 && catToInt.containsKey(categorie[index])){
				catToInt.get(categorie[index]).add(i);
			}
			else{
				List<Integer> gruppi = new LinkedList<Integer>();
				gruppi.add(i);
				catToInt.put(categorie[index], gruppi);
			}
		}
		for(i=0; i<categorie.length; i++){
			if(!catToInt.containsKey(categorie[i])){
				catToInt.put(categorie[i], new LinkedList<Integer>());
			}
		}
		stampa += "\ncategorie assegnate ai K gruppi:\n";
		for(String s: catToInt.keySet()){
			stampa+= "\nclasse: " +s + " -> "+catToInt.get(s).toString();
		}
		this.setStampa(stampa);
		
		return catToInt;
		
	}
	
	/*metodo per inizializzare i K centri in modo che siano abbastanza distanti l'uno dall'altro*/
	public List<Double[]> initCentri(List<Documento> training, int K, double soglia) {
		List<Double[]> centri = new LinkedList<Double[]>();
		int i,k;
		Double[] v = null;
		double[] c_min = null, c_max = null;
		double[] centri1_7 = new double[K-2];
		boolean[] assegnato = new boolean[K-2];
		double s, min=1000, max=0, delta;
		for(k=0; k<assegnato.length; k++){
			assegnato[k] = false;
		}	
		for(Documento d: training){
			s = sommaCompVettore(d.getVector());
			if(s<min){
				min = s;
				c_min = d.getVector();
			}
			if(s>max){
				max = s;
				c_max = d.getVector();
			}
		}
		delta = (max-min)/(K-1);
		for(i=0; i<(K-2); i++){
			centri1_7[i] = min +(i+1)*delta;
		}
		v = convertiVettore(v,c_min);
		centri.add(v);
		v = convertiVettore(v,c_max);
		centri.add(v);
		for(Documento d: training){
			for(i=0; i<(K-2); i++){	
				if((assegnato[i]!=true) && (Math.abs((sommaCompVettore(d.getVector())-centri1_7[i]))<soglia)){
					v = convertiVettore(v, d.getVector());
					centri.add(v);
					assegnato[i] = true;
				}
				if(centri.size()==K){
					return centri;
				}
			}	
		}
		return centri;
	}
	
	public double sommaCompVettore(double[] vettore){
		double sum = 0;
		for(int i=0; i<vettore.length; i++){
			sum += vettore[i];
		}
		return sum;
	}
	
	/*trasforma un vettore di double in un vettore di Double*/
	public Double[] convertiVettore(Double[] v,double[] vettore){
		v = new Double[vettore.length];
		for(int i=0; i<vettore.length; i++){
			v[i] = vettore[i];
		}
		return v;
	}
	
	
	
	public int getK() {
		return K;
	}
	public void setK(int k) {
		K = k;
	}
	
	public String[] getClassi() {
		return classi;
	}

	public void setClassi(String[] classi) {
		this.classi = classi;
	}

	public List<Documento> getDocs() {
		return docs;
	}

	public void setDocs(List<Documento> docs) {
		this.docs = docs;
	}

	public Map<String, List<Documento>> getMap_docs() {
		return map_docs;
	}

	public void setMap_docs(Map<String, List<Documento>> map_docs) {
		this.map_docs = map_docs;
	}
}
